﻿using Chip8.Core.Processor.Operations;

namespace Chip8.Core.Processor
{
	internal static class OpcodeDecoder
	{
		internal static Operation Decode(ushort opcode)
		{
			Operation operation = null;
			//TODO: use flyweight prototypes for operations
			//TODO: replace case labels with consts
			switch (opcode & 0xF000)
			{
				case 0x0000:
					operation = Decode0x(opcode);
					break;
				case 0x1000:
					operation = new JP((ushort)(opcode & 0x0FFF));
					break;
				case 0x2000:
					operation = new CALL((ushort)(opcode & 0x0FFF));
					break;
				case 0x3000:
					operation = new SEv(opcode & 0x0F00, (byte)(opcode & 0x00FF));
					break;
				case 0x4000:
					operation = new SNEv(opcode & 0x0F00, (byte)(opcode & 0x00FF));
					break;
				case 0x5000:
					operation = new SEr(opcode & 0x0F00, opcode & 0x00F0);
					break;
				case 0x6000:
					operation = new LDv(opcode & 0x0F00, (byte)(opcode & 0x00FF));
					break;
				case 0x7000:
					operation = new ADDv(opcode & 0x0F00, (byte)(opcode & 0x00FF));
					break;
				case 0x8000:
					operation = Decode8x(opcode);
					break;
				case 0x9000:
					operation = new SNEr(opcode & 0x0F00, opcode & 0x00F0);
					break;
				case 0xA000:
					operation = new LDi((ushort)(opcode & 0x0FFF));
					break;
				case 0xB000:
					operation = new JPo((ushort)(opcode & 0x0FFF));
					break;
				case 0xC000:
					operation = new RND(opcode & 0x0F00, (byte)(opcode & 0x00FF));
					break;
				case 0xD000:
					operation = new DRW(opcode & 0x0F00, opcode & 0x00F0, opcode & 0x000F);
					break;
				case 0xE000:
					operation = DecodeEx(opcode);
					break;
				case 0xF000:
					operation = DecodeFx(opcode);
					break;
				default:
					throw new IllegalOpcodeException(opcode);
			}

			return operation;
		}

		private static Operation Decode0x(ushort opcode)
		{
			if (opcode == 0x00E0)
			{
				return new CLS();
			}

			if (opcode == 0x00EE)
			{
				return new RET();
			}

			return new SYS((ushort)(opcode & 0x0FFF));
		}

		private static Operation Decode8x(ushort opcode)
		{
			Operation operation = null;
			var regIndex1 = opcode & 0x0F00;
			var regIndex2 = opcode & 0x00F0;
			switch (opcode & 0x000F)
			{
				case 0x0:
					operation = new LDr(regIndex1, regIndex2);
					break;
				case 0x1:
					operation = new OR(regIndex1, regIndex2);
					break;
				case 0x2:
					operation = new AND(regIndex1, regIndex2);
					break;
				case 0x3:
					operation = new SUB(regIndex1, regIndex2);
					break;
				case 0x4:
					operation = new ADDr(regIndex1, regIndex2);
					break;
				case 0x5:
					operation = new SUB(regIndex1, regIndex2);
					break;
				case 0x6:
					operation = new SHR(regIndex1);
					break;
				case 0x7:
					operation = new SUBN(regIndex1, regIndex2);
					break;
				case 0xE:
					operation = new SHL(regIndex1);
					break;
				default:
					throw new IllegalOpcodeException(opcode);
			}

			return operation;
		}

		private static Operation DecodeEx(ushort opcode)
		{
			Operation operation = null;
			var regIndex = opcode & 0x0F00;
			switch (opcode & 0x00FF)
			{
				case 0x9E:
					operation = new SKP(regIndex);
					break;
				case 0xA1:
					operation = new SKNP(regIndex);
					break;
				default:
					throw new IllegalOpcodeException(opcode);
			}

			return operation;
		}

		private static Operation DecodeFx(ushort opcode)
		{
			Operation operation = null;
			var regIndex = opcode & 0x0F00;
			switch (opcode & 0x00FF)
			{
				case 0x07:
					operation = new LDldt(regIndex);
					break;
				case 0x0A:
					operation = new LDk(regIndex);
					break;
				case 0x15:
					operation = new LDsdt(regIndex);
					break;
				case 0x18:
					operation = new LDsst(regIndex);
					break;
				case 0x1E:
					operation = new ADDi(regIndex);
					break;
				case 0x29:
					operation = new LDf(regIndex);
					break;
				case 0x33:
					operation = new LDb(regIndex);
					break;
				case 0x55:
					operation = new LDsmr(regIndex);
					break;
				case 0x65:
					operation = new LDlmr(regIndex);
					break;
				default:
					throw new IllegalOpcodeException(opcode);
			}

			return operation;
		}
	}
}
